Realtime TradingView Chart
System Instructions
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming (keeping your original future-dated data)
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
// Timeframe arrays to store aggregated data
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
// Start streaming data and populating timeframe arrays
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000); // 5 second interval for demonstration
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update all timeframe arrays
for (let timeframe in dataArrays) {
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], newData], timeframe);
}
// Log the updated arrays
console.log("Updated dataArrays:", JSON.parse(JSON.stringify(dataArrays)));
// Notify subscribers about new data
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
const { symbolInfo, resolution, callback } = handler;
const aggregatedData = aggregateData([newData], resolution)[0];
callback(aggregatedData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
// Implement symbol search logic here
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
// Implement symbol resolution logic here
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Log the timeframe array for the selected resolution
console.log(`[getBars]: Timeframe array for ${resolution}:`, dataArrays[resolution]);
// Get data from the appropriate timeframe array
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000; // Convert milliseconds to seconds
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time / 1000, // Set initial time to the first data point
});
</script>
</body>
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
// Start streaming data
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return an empty array for historical data as we are only focused on realtime updates
onHistoryCallback([], { noData: true });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time / 1000,
});
</script>
</body>
</html>
Removed Timeframe Arrays and Aggregation Logic: We removed the dataArrays object and the aggregateData and getTimeframeMs functions because we're only dealing with realtime updates and don't need to store or aggregate data.
Simplified streamData: The streamData function remains the same, simulating a stream of data.
Simplified startStreaming: The startStreaming function now simply iterates through the simulated data stream and calls notifySubscribers with each new data point.
Modified notifySubscribers: The notifySubscribers function now directly passes the newData to the onRealtimeCallback without any aggregation.
Modified getBars: The getBars function now always returns an empty array with noData: true. This indicates to TradingView that there is no historical data available, and it should rely solely on the realtime updates.
The streamData function simulates a stream of new bar data. startStreaming iterates through this stream and sends each new bar to notifySubscribers. notifySubscribers directly calls the onRealtimeCallback provided by TradingView for each subscriber (chart instance). TradingView's replay method internally handles updating the chart based on these realtime updates. Since getBars returns no historical data, the chart initially starts empty and is populated solely by the realtime updates.
time: simulatedData[0].time / 1000,
time: simulatedData[0].time / 1000, // Corrected: Divide by 1000
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<!-- A container for the library widget -->
<div id="tv_chart_container"></div>
<!-- The script that loads Advanced Charts -->
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<!-- Custom datafeed module -->
<script type="module">
// Simulated data for streaming
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
// Start streaming data
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
// Start streaming immediately
startStreaming();
// Subscription handling
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
// DatafeedConfiguration implementation
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
// Modified Datafeed object
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return an empty array for historical data as we are only focused on realtime updates
onHistoryCallback([], { noData: true });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
// TradingView widget initialization
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
time: simulatedData[0].time * 1000,
});
</script>
</body>
Remove the time option entirely: TradingView should automatically adjust the initial time based on the first data point it receives.
window.tvWidget = new TradingView.widget({ // ... (rest of your configuration) // Remove the 'time' option });Double-check the timezone: Make sure the timezone specified in your resolveSymbol function ('Etc/UTC' in your code) matches the timezone of your simulatedData. Any discrepancies can lead to incorrect date display.
Inspect the network requests: Use your browser's developer tools (Network tab) to monitor the requests made by TradingView. Look for any errors or unexpected responses related to time or data retrieval.
Test with a simpler dataset: Create a very basic simulatedData array with only a few data points to isolate whether the issue is with the data itself or with the way TradingView is processing it.
Consult TradingView's documentation: There might be specific guidelines or limitations regarding the initial time setting that are not readily apparent.
subscribeBars is called: When you initialize the TradingView widget and set the data_status to streaming in the resolveSymbol function, TradingView calls your subscribeBars method.onRealtimeCallback is set: In subscribeBars, you provide a callback function (onRealtimeCallback) that TradingView will use to send you new data updates.New data arrives: Your notifySubscribers function, which is triggered by your simulated data stream, sends new data to TradingView through the onRealtimeCallback.TradingView processes the data: TradingView internally receives the new data and determines how to update the chart based on the resolution and timeframe.replay is called (internally): TradingView's internal logic calls the replay function, which is responsible for updating the chart's data and redrawing it to reflect the new information. This is done without you directly calling the replay function.
You: The data provider, sending updates like a news feed.TradingView Charting Library: The newsreader, receiving updates and deciding how to present them on the screen.replay function: The internal mechanism the newsreader uses to update the display based on new information.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
// ... (rest of your simulated data)
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
// ... (other timeframes)
};
function streamData(data, interval = 5000) {
// ... (same as before)
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update only the 5S array for now
dataArrays['5S'].push(newData);
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
// ... (same as before)
}
const configurationData = {
// ... (same as before)
};
const Datafeed = {
onReady: (callback) => {
// ... (same as before)
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
// ... (same as before)
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
// ... (same as before)
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
// Return data from the appropriate timeframe array
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
// ... (same as before)
},
unsubscribeBars: (subscriberUID) => {
// ... (same as before)
},
};
window.tvWidget = new TradingView.widget({
// ... (rest of your widget configuration)
});
</script>
</body>
</html>
dataArrays Object: We reintroduce the dataArrays object to store data for each timeframe separately. For now, we only populate the '5S' array.startStreaming Modification: In startStreaming, we now push each new data point into the dataArrays['5S'] array.getBars Modification: Instead of returning an empty array, we now filter the appropriate timeframe array (dataArrays[resolution]) based on the requested from and to timestamps. This ensures that when you switch timeframes and then back to 5S, you get all the data that has been accumulated in the dataArrays['5S'] array up to that point.
The replay stream (via onRealtimeCallback) continues to populate the dataArrays['5S'] array. When you switch to a different timeframe, TradingView calls getBars with the new resolution and time range. getBars returns the relevant data from the corresponding timeframe array (which might be empty for other timeframes initially). When you switch back to 5S, getBars will return all the data that has been accumulated in dataArrays['5S'], giving you a continuous view of the 5S data.
You'll need to implement logic to populate the other timeframe arrays ('1', '3', etc.) as well, possibly using aggregation or other methods, depending on how you want to handle different resolutions. Consider how you'll handle historical data for timeframes other than 5S – you might need to fetch it initially or provide a way to load it on demand.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 25126.1, volume: 0 },
{ time: 1725594310000, open: 25121.85, high: 25121.85, low: 25114.2, close: 25117.85, volume: 0 },
{ time: 1725594315000, open: 25115.6, high: 25118.2, low: 25111.25, close: 25114.4, volume: 0 },
{ time: 1725594320000, open: 25117.6, high: 25117.6, low: 25108.75, close: 25108.75, volume: 0 },
{ time: 1725594325000, open: 25108.5, high: 25122.85, low: 25108.25, close: 25122.85, volume: 0 },
{ time: 1725594330000, open: 25121.4, high: 25125.3, low: 25118.0, close: 25118.0, volume: 0 },
{ time: 1725594335000, open: 25118.2, high: 25118.65, low: 25116.7, close: 25118.35, volume: 0 }
];
const dataArrays = {
'5S': [],
'1': [],
'3': [],
'5': [],
'15': [],
'30': [],
'60': [],
'120': [],
'240': [],
'1D': [],
'1W': [],
'1M': []
};
function streamData(data, interval = 5000) {
let index = 0;
return {
next() {
return new Promise((resolve) => {
if (index < data.length) {
setTimeout(() => {
resolve({ value: data[index++], done: false });
}, interval);
} else {
resolve({ done: true });
}
});
},
[Symbol.asyncIterator]() {
return this;
}
};
}
function aggregateData(data, timeframe) {
const aggregated = [];
let currentBucket = null;
const timeframeMs = getTimeframeMs(timeframe);
for (let row of data) {
const bucketTime = Math.floor(row.time / timeframeMs) * timeframeMs;
if (!currentBucket || currentBucket.time !== bucketTime) {
if (currentBucket) {
aggregated.push(currentBucket);
}
currentBucket = {
time: bucketTime,
open: row.open,
high: row.high,
low: row.low,
close: row.close,
volume: row.volume
};
} else {
currentBucket.high = Math.max(currentBucket.high, row.high);
currentBucket.low = Math.min(currentBucket.low, row.low);
currentBucket.close = row.close;
currentBucket.volume += row.volume;
}
}
if (currentBucket) {
aggregated.push(currentBucket);
}
return aggregated;
}
function getTimeframeMs(timeframe) {
const unit = timeframe.slice(-1);
const value = parseInt(timeframe);
switch (unit) {
case 'S': return value * 1000;
case 'D': return value * 24 * 60 * 60 * 1000;
case 'W': return value * 7 * 24 * 60 * 60 * 1000;
case 'M': return value * 30 * 24 * 60 * 60 * 1000;
default: return value * 60 * 1000; // Assume minutes if no unit specified
}
}
async function startStreaming() {
const dataStream = streamData(simulatedData, 5000);
for await (const newData of dataStream) {
console.log("New data received:", new Date(newData.time), newData);
// Update all timeframe arrays
for (let timeframe in dataArrays) {
dataArrays[timeframe] = aggregateData([...dataArrays[timeframe], newData], timeframe);
}
notifySubscribers(newData);
}
console.log("Streaming completed");
}
startStreaming();
const subscribers = new Map();
function notifySubscribers(newData) {
for (let [subscriberUID, handler] of subscribers) {
handler.callback(newData);
}
}
const configurationData = {
supported_resolutions: ["5S", "1", "3", "5", "15", "30", "60", "120", "240", "1D", "1W", "1M"],
exchanges: [{
value: 'Kraken',
name: 'Kraken',
desc: 'Kraken bitcoin exchange',
}],
symbols_types: [{
name: 'crypto',
value: 'crypto',
}],
};
const Datafeed = {
onReady: (callback) => {
console.log('[onReady]: Method call');
setTimeout(() => callback(configurationData));
},
searchSymbols: (userInput, exchange, symbolType, onResultReadyCallback) => {
console.log('[searchSymbols]: Method call');
onResultReadyCallback([]);
},
resolveSymbol: (symbolName, onSymbolResolvedCallback, onResolveErrorCallback) => {
console.log('[resolveSymbol]: Method call', symbolName);
const symbolInfo = {
name: symbolName,
description: symbolName,
type: 'crypto',
session: '24x7',
timezone: 'Etc/UTC',
minmov: 1,
pricescale: 100,
has_intraday: true,
has_seconds: true,
intraday_multipliers: ['1', '60'],
seconds_multipliers: ["5"],
supported_resolutions: configurationData.supported_resolutions,
volume_precision: 2,
data_status: 'streaming',
};
onSymbolResolvedCallback(symbolInfo);
},
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
},
subscribeBars: (symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) => {
console.log('[subscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.set(subscriberUID, { symbolInfo, resolution, callback: onRealtimeCallback });
},
unsubscribeBars: (subscriberUID) => {
console.log('[unsubscribeBars]: Method call with subscriberUID:', subscriberUID);
subscribers.delete(subscriberUID);
},
};
window.tvWidget = new TradingView.widget({
symbol: 'Kraken:BTC/USD',
interval: '5S',
fullscreen: true,
container: 'tv_chart_container',
datafeed: Datafeed,
enabled_features: ["dont_show_boolean_study_arguments", "seconds_resolution"],
library_path: '../charting_library_cloned_data/charting_library/',
time_frames: [
{ text: "1m", resolution: "1", description: "1 Minute", title: "1m" },
{ text: "3m", resolution: "3", description: "3 Minutes", title: "3m" },
{ text: "5m", resolution: "5", description: "5 Minutes", title: "5m" },
{ text: "15m", resolution: "15", description: "15 Minutes", title: "15m" },
{ text: "30m", resolution: "30", description: "30 Minutes", title: "30m" },
{ text: "1h", resolution: "60", description: "1 Hour", title: "1h" },
{ text: "2h", resolution: "120", description: "2 Hours", title: "2h" },
{ text: "4h", resolution: "240", description: "4 Hours", title: "4h" },
{ text: "1D", resolution: "1D", description: "1 Day", title: "1D" },
{ text: "1W", resolution: "1W", description: "1 Week", title: "1W" },
{ text: "1M", resolution: "1M", description: "1 Month", title: "1M" },
],
overrides: {
"mainSeriesProperties.candleStyle.upColor": "#26a69a",
"mainSeriesProperties.candleStyle.downColor": "#ef5350",
"mainSeriesProperties.candleStyle.drawWick": true,
"mainSeriesProperties.candleStyle.drawBorder": true,
"mainSeriesProperties.candleStyle.borderColor": "#378658",
"mainSeriesProperties.candleStyle.borderUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.borderDownColor": "#ef5350",
"mainSeriesProperties.candleStyle.wickUpColor": "#26a69a",
"mainSeriesProperties.candleStyle.wickDownColor": "#ef5350",
},
// Removed the 'time' option to let TradingView auto-adjust
});
</script>
</body>
</html>
Aggregation for All Timeframes: The startStreaming function now uses the aggregateData function to populate all the timeframe arrays in dataArrays whenever a new data point is received.getBars Returns Aggregated Data: The getBars function now returns the appropriate aggregated data from the dataArrays object based on the requested resolution and time range.Timeframes Added to Widget Configuration: The time_frames option in the TradingView widget configuration now includes all the supported timeframes, allowing users to switch between them.Removed time Option: The time option has been removed from the widget configuration, allowing TradingView to automatically adjust the initial time based on the first data point it receives.
Data Streaming and Aggregation: The startStreaming function simulates a stream of new data. For each new data point, it updates all the timeframe arrays in dataArrays using the aggregateData function, which aggregates the data based on the timeframe.Chart Initialization: When the TradingView chart is initialized, it requests historical data from getBars.getBars Provides Data: The getBars function returns the aggregated data for the requested timeframe and time range from the dataArrays object.Realtime Updates: As new data arrives through the onRealtimeCallback, TradingView updates the chart using the replay function internally. The dataArrays are also updated in real-time, ensuring that when you switch between timeframes, you see the correct aggregated data.
getBars: (symbolInfo, resolution, periodParams, onHistoryCallback, onErrorCallback) => {
const { from, to, firstDataRequest } = periodParams;
console.log('[getBars]: Method call', symbolInfo, resolution, new Date(from * 1000), new Date(to * 1000));
if (resolution === '5S' && firstDataRequest) {
// If it's the first request for 5S, return the entire array
const bars = dataArrays['5S'];
console.log(`[getBars]: returned entire 5S array (${bars.length} bar(s))`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
} else {
// For other requests (including subsequent 5S requests), filter as usual
const bars = dataArrays[resolution].filter(bar => {
const barTime = bar.time / 1000;
return barTime >= from && barTime < to;
});
console.log(`[getBars]: returned ${bars.length} bar(s)`, bars);
onHistoryCallback(bars, { noData: bars.length === 0 });
}
},
We now check if resolution === '5S' and firstDataRequest === true. If both conditions are met, it means it's the initial request for the 5S timeframe, and we return the entire dataArrays['5S'] array. Otherwise, we filter the array based on from and to as before.
<!DOCTYPE HTML>
<html>
<head>
<title>TradingView Advanced Charts with Replay API</title>
</head>
<body style="margin:0px;">
<div id="tv_chart_container"></div>
<script type="text/javascript" src="charting_library_cloned_data/charting_library/charting_library.js"></script>
<script type="module">
const simulatedData = [
{ time: 1725594300000, open: 25093.7, high: 25120.4, low: 25088.4, close: 25113.95, volume: 0 },
{ time: 1725594305000, open: 25120.65, high: 25131.65, low: 25118.15, close: 2512